﻿using System.Text;
using System.Threading.Tasks;
using K9Nano.AspNetCore.Authorization;
using K9Nano.AspNetCore.Filters;
using K9Nano.AspNetCore.Security;
using K9Nano.Authorization;
using K9Nano.Captcha;
using K9Nano.Captcha.Sms;
using K9Nano.Domain;
using K9Nano.Domain.Authorization;
using K9Nano.Exceptions;
using K9Nano.Extensions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;

namespace K9Nano.AspNetCore;

public static class K9NanoWebStartupExtensions
{
    public static IK9AppBuilder AddK9NanoWeb(this IK9AppBuilder builder,
        IConfiguration configuration,
        Action<AuthenticationBuilder>? authenticationConfigure = null,
        Action<AuthorizationOptions>? authorizationConfigure = null)
    {
        #region Authentication

        var jwtOptions = new JwtOptions();
        configuration.GetSection("Jwt").Bind(jwtOptions);
        var authenticationBuilder = builder.Services
              .Configure<JwtOptions>(o => o.Clone(jwtOptions))
              .AddSingleton<IJwtHelper, JwtHelper>()
              .AddAuthentication(options =>
              {
                  options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
              })
              .AddJwtBearer(options =>
              {
                  options.TokenValidationParameters = new TokenValidationParameters()
                  {
                      ValidateIssuer = true,
                      ValidIssuer = jwtOptions.Issuer,
                      ValidateAudience = true,
                      ValidAudience = jwtOptions.Audience,
                      ValidateIssuerSigningKey = true,
                      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecretKey)),
                      ValidateLifetime = true,
                      ClockSkew = TimeSpan.FromSeconds(30),
                      RequireExpirationTime = true,
                  };

                  options.Events = new JwtBearerEvents
                  {
                      OnMessageReceived = context =>
                      {
                          if (string.IsNullOrEmpty(context.Request.Headers.Authorization))
                          {
                              var token = context.Request.Query["access_token"];
                              if (!string.IsNullOrEmpty(token))
                              {
                                  context.Request.Headers.Authorization = $"Bearer {token}";
                              }
                          } 
                          return Task.CompletedTask;
                      }
                  };
              });

        authenticationConfigure?.Invoke(authenticationBuilder);

        builder.Services.AddScoped<IAuthorizationHandler, K9RbacRequirementHandler>();

        builder.Services.AddAuthorization(options =>
        {
            options.AddPolicy(RbacOptions.Policy, policy => policy.AddRequirements(new K9RbacRequirement()));

            authorizationConfigure?.Invoke(options);
        });

        #endregion

        #region Filters

        builder.Services.Configure<ApiBehaviorOptions>(opt =>
        {
            opt.SuppressModelStateInvalidFilter = true;
        });

        /* Filters */
        builder.Services.Configure<MvcOptions>(options =>
        {
            options.Filters.Add<K9ExceptionFilter>();
            options.Filters.Add<K9ActionFilter>();
        });

        #endregion

        #region CORS

        var allowedCors = configuration["CORS:Name"];
        var allowedCorsOrigins = configuration.GetSection("CORS:Origins").Get<string[]>();
        var corsEnabled = !string.IsNullOrEmpty(allowedCors) && allowedCorsOrigins?.Length > 0;

        if (corsEnabled)
        {
            builder.Services.AddCors(options =>
            {
                options.AddPolicy(name: allowedCors!,
                    policy =>
                    {
                        policy.WithOrigins(allowedCorsOrigins!)
                            .AllowAnyHeader()
                            .AllowAnyMethod();
                    });
            });
        }

        #endregion

        return builder;
    }

    public static IApplicationBuilder UseApiAsPermissions(
       this IApplicationBuilder builder)
    {
        ActionPermissionProvider.CreatePermissions(builder.ApplicationServices);
        FrontendRoutes.CreatePermissions(builder.ApplicationServices.GetRequiredService<IPermissionCatalog>());
        return builder;
    }

    public static IApplicationBuilder UseRequestLogging(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<LoggingMiddleware>();
    }

    [Obsolete]
    public static IEndpointRouteBuilder UseCaptcha(
      this IEndpointRouteBuilder builder,
      string requestPath = "/api/captcha",
      int expiresInMinutes = 5,
      int length = 4)
    {
        builder.MapGet(requestPath + "/png/{requestId:required}", async (string requestId, ICaptchaManager captchaManager, HttpContext context) =>
        {
            if (string.IsNullOrEmpty(requestId))
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsJsonAsync(ApiResponse.BadRequest("请求ID无效"), context.RequestAborted);
                return;
            }

            await SafeInvokeAsync(context, async () =>
            {
                var captchaInfo = captchaManager.GenerateImage(requestId!, TimeSpan.FromMinutes(expiresInMinutes), length);
                await context.Response.WriteAsJsonAsync(ApiResponse.FromDto(captchaInfo), context.RequestAborted);
            });
        });

        builder.MapGet(requestPath + "/sms/template/{templateId:required}/{phone:required}/code/{code:required}",
            async (string phone, string templateId, string code, HttpContext context) =>
        {
            // code: 图片验证码
            if (string.IsNullOrEmpty(phone) || string.IsNullOrEmpty(code))
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsJsonAsync(ApiResponse.BadRequest("手机号或者验证码不能为空"), context.RequestAborted);
                return;
            }
            var captchaManager = context.RequestServices.GetRequiredService<ICaptchaManager>();
            if (!captchaManager.Validate(phone, code))
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsJsonAsync(ApiResponse.BadRequest("验证码无效"), context.RequestAborted);
                return;
            }
            await SafeInvokeAsync(context, async () =>
            {
                var smsManager = context.RequestServices.GetRequiredService<ISmsCaptchaManager>();
                var result = await smsManager.SendAsync(phone, templateId, TimeSpan.FromMinutes(expiresInMinutes), length)
                ? ApiResponse.Ok : ApiResponse.BadRequest("发送失败");
                await context.Response.WriteAsJsonAsync(result, context.RequestAborted);
            });
        });
        return builder;

        static async Task SafeInvokeAsync(HttpContext context, Func<Task> action)
        {
            try
            {
                await action();
            }
            catch (FriendlyException ex)
            {
                await context.Response.WriteAsUtf8Async(ex.Message, 400, context.RequestAborted);
            }
            catch (Exception ex)
            {
                var logger = context.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("K9Nano.Captcha");
                logger.LogError(ex, "验证码接口异常");
                context.Response.StatusCode = 500;
                await context.Response.WriteAsJsonAsync(new ApiResponse(500, "请求失败"), context.RequestAborted);
            }
        }
    }
}
